﻿using System;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Reflection;

namespace ScreenMonkey.Remote
{
    /// <summary>
    /// Abstract base class for classes which need property notification 
    /// support. By using this class the property notification mechanism
    /// gets applied uniformly in the application.
    /// 
    /// It's the same class as used in the CeetGui, just copied over here.
    /// Thus it should become part of a common library in the future.
    /// </summary>
    public abstract class NotificationBase : INotifyPropertyChanged
    {
        /// <summary>
        /// Raised when a property on this object has a new value.
        /// By declaring the event as a delegate the event will never be
        /// null, so one can safely raise it without null checking.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged = delegate { };

        /// <summary>
        /// Raises this object's PropertyChanged event for the given property
        /// identified by its name string.
        /// </summary>
        protected void RaisePropertyChanged(string propertyName)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        /// <summary>
        /// Raises this object's PropertyChanged event for the given property
        /// identified by a lambda expression instead of a string.
        /// </summary>
        protected void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpression)
        {
            var propertyName = ExtractPropertyName(propertyExpression);
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        /// <summary>
        /// The overloaded RaisePropertyChanged method of the ViewModelBase class which
        /// accepts a lambda instead of a string, uses a bit of reflection to extract
        /// the propertyname itself.
        /// 
        /// \code
        ///
        /// Usage:
        ///
        /// RaisePropertyChanged("Employees");           // conventional
        ///
        /// RaisePropertyChanged(()=>this.Employees);    // enhanced
        /// 
        /// \endcode
        ///
        /// This means intellisense and no more runtime errors, although there is
        /// a slight performance issue due to the Reflection. The method is public
        /// so it can be used also for situations where just the property name is
        /// needed without a property notification.
        /// </summary>
        public string ExtractPropertyName<T>(Expression<Func<T>> propertyExpression)
        {
            if (propertyExpression == null)
            {
                throw new ArgumentNullException("propertyExpression");
            }

            var memberExpression = propertyExpression.Body as MemberExpression;
            if (memberExpression == null)
            {
                throw new ArgumentException("The expression is not a member access expression.", "propertyExpression");
            }

            var property = memberExpression.Member as PropertyInfo;
            if (property == null)
            {
                throw new ArgumentException("The member access expression does not access a property.", "propertyExpression");
            }

            if (!property.DeclaringType.IsAssignableFrom(this.GetType()))
            {
                throw new ArgumentException("The referenced property belongs to a different type.", "propertyExpression");
            }

            var getMethod = property.GetGetMethod(true);

            if (getMethod == null)
            {
                // this shouldn't happen - the expression would reject the property before reaching this far
                throw new ArgumentException("The referenced property does not have a get method.", "propertyExpression");
            }

            if (getMethod.IsStatic)
            {
                throw new ArgumentException("The referenced property is a static property.", "propertyExpression");
            }

            return memberExpression.Member.Name;
        }
    }
}
